module Parameter
   use dateutils
   use strutils
   use Index
   implicit none
!  File: Parameter.pas
!   Purpose: Contains the parameter structures and associated routines
!      The parameter categories available are:
!         1. N last year
!         2. N Last age
!         3. F year factor
!         4. Selection
!         5. Natural mortality
!         6. Catchability at age
!         7. Biomass catchability
!         8. SSB catchability
!      Each parameter is a cube with dimensions:
!         1. Index
!         2. Year
!         3. Age
!      A element in this structure consists of value, flag and description.
!   Note:
!      Interfaces refer to how much knowledge the compiler has about an procedure during compilation.
!      If the interface is explicit, then the compiler can verify that the subprogram is being called
!      correctly. If, however, the interface is implicit, then the Fortran 90 compiler has no information
!      about the types and number of the subprogram's arguments or the return value of the result for
!      a function.
!   History: When        Who        What
!            22-04-07    Skalevik   Created
!

! String length specifiers
integer, parameter :: c_MetaParamLen = 30
integer, parameter :: c_FormatItemLen = 20
integer, parameter :: c_FileNameLen = 100



!--Meta parameter-------------
   type TMetaParam
      character(len = c_MetaParamLen) :: Text;
      Integer :: DimMask;
   end type TMetaParam;
   

! Parameter dimensions
integer, parameter :: cIndex = Z'01';
integer, parameter :: cYear =  Z'02';
integer, parameter :: cAge =   Z'04';

type(TMetaParam), parameter :: c_MetaParamSetup(cFirstCategory : cLastCategory) = (/ &
      TMetaParam('N values',                               cYear + cAge ),         &
      TMetaParam('F year factor',                          cYear        ),         &
      TMetaParam('Selection',                              cYear + cAge ),         &
      TMetaParam('Natural mortality',                      cYear + cAge ),         &
      TMetaParam('Fleet catchability factor',     cIndex + cYear + cAge ),         &
      TMetaParam('SSB catchability factor',       cIndex + cYear        ),         &
      TMetaParam('Biomass catchability factor',   cIndex + cYear        ),         &
      TMetaParam('Fleet catchability power',      cIndex + cYear + cAge ),         &
      TMetaParam('SSB catchability power',        cIndex + cYear        ),         &
      TMetaParam('Biomass catchability power',    cIndex + cYear        ),         &
      TMetaParam('Canum gamma shape',             cIndex                ),         &
      TMetaParam('Fleet gamma shape',             cIndex                ),         &
      TMetaParam('SSB gamma shape',               cIndex                ),         &
      TMetaParam('Biomass gamma shape',           cIndex                )         &
      /)

type TParamFormatItem
      character(Len = c_FormatItemLen) :: Text
      Integer :: Width;
end type


! Parameter format
integer, parameter :: pfCategory = 0;
integer, parameter :: pfText = 1;
integer, parameter :: pfIndex = 2;
integer, parameter :: pfYear = 3;
integer, parameter :: pfAge = 4;
integer, parameter :: pfFlag = 5;
integer, parameter :: pfValue = 6;
integer, parameter :: pfFirst = pfCategory;
integer, parameter :: pfLast = pfValue;


! Parameter format setup
type(TParamFormatItem), parameter :: c_ParFmt(pfFirst : pfLast) = (/ &
   TParamFormatItem('Category', 3), &
   TParamFormatItem('Text',    60), &
   TParamFormatItem('Index',    4), &
   TParamFormatItem('Year',     4), &
   TParamFormatItem('Age',      4), &
   TParamFormatItem('Flag',     3), &
   TParamFormatItem('Value',    16)  &
   /)
   

contains


function GetParamElm(Opt, Cat, iIndex, iYear, iAge, Check)
!  Purpose: This procedure returns the parameter elm located at given dimension indexes
!   In:      Cat: Category
!            iIndex: Index 0..NumIndices - 1
!            iYear:
!            iAge:
!   Assume:  Dimension indexes are inside its range.
!   Promise: Ptr to Parameter elm object.
!   History: When        Who        What
!            22-04-07    Skalevik   Created
!
   implicit none
   type(TParamElm), pointer :: GetParamElm
   type(TOptimisation), intent(in) :: Opt
   integer :: Cat, iIndex, iYear, iAge, NYears
   logical :: Check

   Logical :: Accepted = .true.
   type(TParamCube), pointer :: pParamCube

   GetParamElm => null()
   if (.not. associated(Opt%ParamCubes)) return
   pParamCube => Opt%ParamCubes(Cat)
   if (Check) then
      NYears = min(NumYears(Opt%IndexInput), size(pParamCube%Elm, 2))
      Accepted = &
         ((cFirstCategory <= Cat) .and. (Cat <= cLastCategory)) .and. &
         ((0 <= iIndex) .and. (iIndex <= (size(pParamCube%Elm, 1) - 1))) .and. &
         ((0 <= iYear) .and. (iYear <= (NYears - 1))) .and. &
         ((0 <= iAge) .and. (iAge <= (size(pParamCube%Elm, 3) - 1)))
!        if (.not. Accepted) then
!            call MessageOut("Index is wrong: " // inttostr(Cat) // ", " // inttostr(iIndex) // &
!               ", " // inttostr(iYear) // ", " // inttostr(iAge))
!         else
      if (Accepted) then
         GetParamElm => pParamCube%Elm(iIndex, iYear, iAge)
      end if
!         endif
   else
      GetParamElm => pParamCube%Elm(iIndex, iYear, iAge)
   end if
end function GetParamElm


function SupportDim(Cat, Dim)
!  Purpose: This function returns true if the given dimension Dim is applicable
!           for the given category Cat.
!  In:      Cat - category
!           Dim - dimension
!  Assume:  Cat and Dim are inside its range.
!  Promise: True if dimension is applicable, false if not.
!  History: When        Who        What
!           22-04-07    Skalevik   Created
!
   implicit none
   logical :: SupportDim
   Integer, intent(in) :: Cat, Dim
   SupportDim = (iand(Dim, c_MetaParamSetup(Cat)%DimMask) .ne. 0)
end function SupportDim


function DimIndexToStr(IndexInput, Cat, Dim, DimIndex)
!   Purpose: This function returns the index description the given
!            category Cat and dimension Dim.
!   In:      Cat - category
!            Dim - dimension
!   Assume:  Cat and Dim are inside its ranges.
!   Promise: Dimension index description.
!   Note:    First index, year and age are appended to the index.
!            If category dimension isnt applicable, the result is empty
!   History: When        Who        What
!            22-04-07    Skalevik   Created
!
   implicit none
   type(TIndexInput), intent(in) :: IndexInput
   integer, intent(in) :: Cat, Dim, DimIndex
   character(len = 4) :: DimIndexToStr
   Integer :: DimValue
   DimIndexToStr = ""
   if (SupportDim(Cat, Dim)) then
      DimValue = DimIndex
      select case(Dim)
         case(cIndex)
            DimValue = DimIndex + FirstIndex()
         case(cYear)
            DimValue = DimIndex + IndexInput%FirstYear
         case(cAge)
            DimValue = DimIndex + IndexInput%FirstAge
      end select
      write(DimIndexToStr, "(I4)") DimValue
   endif
end function DimIndexToStr


function StrToDimIndex(IndexInput, S, Dim)
!   Purpose: This function returns the dimension index from a string
!   History: When        Who        What
!            23-04-07    Skalevik   Created
!
   implicit none
   type(TIndexInput), intent(in) :: IndexInput
   character(len=*), intent(in) :: S
   integer, intent(in) :: Dim
   integer :: StrToDimIndex
   integer, parameter :: c_DefaultDimIndex = 0

   StrToDimIndex = c_DefaultDimIndex
   if (trim(S) .ne. "") then
      read(S, "(I4)") StrToDimIndex
      select case(Dim)
         case(cIndex)
            StrToDimIndex = StrToDimIndex - FirstIndex()
         case(cYear)
            StrToDimIndex = StrToDimIndex - IndexInput%FirstYear
         case(cAge)
            StrToDimIndex = StrToDimIndex - IndexInput%FirstAge
      end select
   endif
end function StrToDimIndex


function ParFmtColStart(ParFmt)
!   Purpose: This function returns the start column of a parameter format item
!   History: When        Who        What
!            23-04-07    Skalevik   Created
!
   implicit none
   integer, intent(in) :: ParFmt
   integer :: ParFmtColStart
   integer :: iFmt
   ParFmtColStart = 1
   do iFmt = pfFirst, ParFmt - 1
      ParFmtColStart = ParFmtColStart + c_ParFmt(iFmt)%Width
   end do
end function ParFmtColStart


subroutine SaveParameters(Opt, SaveModelOutput)
!   Purpose: This procedure saves the parameters to a parameter file
!   History: When        Who        What
!            23-04-07    Skalevik   Created
!
   implicit none
   type(TOptimisation), intent(in) :: Opt
   logical, intent(in) :: SaveModelOutput
   Integer :: Cat, iIndex, iYear, iAge, NIndices, NYears, NAges, &
      iFmt, iStart, Width
   character(len = 1) :: FormatType
   character(len = 50) :: FormatStr
   character(len=19) :: datestr
   character(len = 4) :: sIndex, sYear, sAge
   character(len = 3) :: sCat, sFlag
   character(len = 16) :: sValue
   character(len = 256) :: FileName
   type(TParamElm), pointer :: pParamElm
   type(TParamCube), pointer :: pParamCube
   integer :: ioerr 
   logical :: SaveParamToFile

   FormatStr = '('
   do iFmt = pfFirst, pfLast
      FormatStr = lrtrim(FormatStr) // lrtrim('A') // lrtrim(inttostr(c_ParFmt(iFmt)%Width));
   enddo
   FormatStr = lrtrim(FormatStr) // lrtrim(')')
!   FormatStr = '(A3A60A4A4A4A3A16)'
   if(SaveModelOutput) then
      FileName = lrtrim(GetWorkFileName(Opt, Opt%IndexInput%OutFiles(roParamOut)))
   else
      FileName = GetWorkFileName(Opt, Opt%IndexInput%InParamFileName)
   endif
   open(cFileUnit, file = FileName, action = 'write', iostat = ioerr, status = 'replace')
   if (io_no_error .ne. ioerr) then
      call MessageOut(Opt, "IO error:" // inttostr(ioerr))
      return
   endif
   datestr = datetimestr()
   write(cFileUnit, "(A)") lrtrim("!Parameter file created by Parameter editor " // datestr)
   write(cFileUnit, *)
   write(cFileUnit, "(A)") "! From To   # Name"
   do iFmt = pfFirst, pfLast
      iStart = ParFmtColStart(iFmt)
      Width = c_ParFmt(iFmt)%Width
      write(cFileUnit, "(A1I3A2I3I4A20)") '!', iStart, "..", &
         iStart + Width - 1, Width, " " // c_ParFmt(iFmt)%Text
   enddo
   write(cFileUnit, *)
   write(cFileUnit, "(A)") lrtrim('!-------------------------------------------------------------------------------------')
   do Cat = cFirstCategory, cLastCategory
      pParamCube => Opt%ParamCubes(Cat)
      NIndices = size(pParamCube%Elm, 1)
      do iIndex = 0, NIndices - 1
         NYears = size(pParamCube%Elm, 2)
         do iYear = 0, NYears - 1
            NAges = size(pParamCube%Elm, 3)
            do iAge = 0, NAges - 1
               SaveParamToFile = (iYear <= NumYears(Opt%IndexInput) - 1)
               if (.not. SaveParamToFile) cycle
               select case(Cat) 
                  case(cNValues)
                     SaveParamToFile = &
                        (iAge <= NumTrueAges(Opt%IndexInput) - 1) .and. &
                        ( &
                        (iAge == NumTrueAges(Opt%IndexInput) - 1) .or. &
                        (iYear == NumYears(Opt%IndexInput) - 1) .or. &
                        (iYear == NumCatchYears(Opt%IndexInput) - 1) .or. &
                        ((iAge == 0) .and. (iYear > NumCatchYears(Opt%IndexInput) - 1)) &
                        ) 
               end select
               if (.not. SaveParamToFile) cycle
               pParamElm => pParamCube%Elm(iIndex, iYear, iAge)
               write(sCat, '(i3)') Cat
               sIndex = DimIndexToStr(Opt%IndexInput, Cat, cIndex, iIndex)
               sYear = DimIndexToStr(Opt%IndexInput, Cat, cYear, iYear)
               sAge = DimIndexToStr(Opt%IndexInput, Cat, cAge, iAge)
               write(sFlag, '(i3)') pParamElm%Flag
               select case (Cat)
                  case default
                     if((pParamElm%Value > 1e9) .or. ((pParamElm%Value > 0.0d0) .and. &
                        (pParamElm%Value < 0.00001d0))) then
                        write(sValue,"(ES16.9E3)") pParamElm%Value
                     else
                        write(sValue,"(F16.6)") pParamElm%Value
                     endif
               end select
               write(cFileUnit, FormatStr) sCat, pParamElm%Descr, sIndex, sYear, sAge, sFlag, sValue
            enddo
         enddo
      enddo
   enddo
   close(cFileUnit)
end subroutine SaveParameters


function LoadParameters(Opt)
!(* Purpose: This procedure loads the parameters from parameter file
!   Assume:  File exists and is a legal parameter file according to index file
!   Promise: Parameters loaded
!   History: When        Who        What
!            22-04-07    Skalevik   Created
!*)
   implicit none
   type(TOptimisation), intent(inout) :: Opt
   character(len = 4) :: sIndex, sYear, sAge
   character(len = 94) :: s
   character(len = 256) :: F
   
   Integer :: Cat, iIndex, iYear, iAge, ioerr
   type(TParamElm), pointer :: pParamElm
   logical :: LoadParameters
   
   LoadParameters = .true.

   ! Reallocate parameter structure
   call DeallocateParameters(Opt)
   call InitializeParameterStructures(Opt)

   F = Opt%IndexInput%InParamFileName
   open(cFileUnit, file = GetWorkFileName(Opt, F), action = 'read', iostat = ioerr, status = 'old')
   if (io_no_error .ne. ioerr) then
      call MessageOut(Opt, "IO error:" // inttostr(ioerr))
      return
   endif
   do
      read(cFileUnit, '(A94)', iostat=ioerr) s
      if (io_no_error .ne. ioerr) then
         exit
      endif
      if (0 < len(lrtrim(s))) then
         if (s(1:1) .eq. '!') then
            cycle
         endif
         Cat = strtoint(s(ParFmtColStart(pfCategory) : ParFmtColStart(pfCategory) + c_ParFmt(pfCategory)%Width - 1))
         sIndex = lrtrim(s(ParFmtColStart(pfIndex) : ParFmtColStart(pfIndex) + c_ParFmt(pfIndex)%Width - 1))
         sYear = lrtrim(s(ParFmtColStart(pfYear) : ParFmtColStart(pfYear) + c_ParFmt(pfYear)%Width - 1))
         sAge = lrtrim(s(ParFmtColStart(pfAge) : ParFmtColStart(pfAge) + c_ParFmt(pfAge)%Width - 1))
         iIndex = StrToDimIndex(Opt%IndexInput, sIndex, cIndex)
         iYear = StrToDimIndex(Opt%IndexInput, sYear, cYear)
         iAge = StrToDimIndex(Opt%IndexInput, sAge, cAge)
         pParamElm => GetParamElm(Opt, Cat, iIndex, iYear, iAge, .true.)
         if (associated(pParamElm)) then
            pParamElm%Flag = strtoint(s(ParFmtColStart(pfFlag) : ParFmtColStart(pfFlag) + c_ParFmt(pfFlag)%Width - 1))
            ! Check the flag range
            if ((pParamElm%Flag < flEqualPrevAge) .or. (pParamElm%Flag > flSpecialCode)) then
               Opt%RunErrorStr = "Parameter flag not defined: " // lrtrim(c_MetaParamSetup(Cat)%Text) // ", index:" // &
                  lrtrim(sIndex) // ", year:" // lrtrim(sYear) // ", age:" // lrtrim(sAge) // ", flag:" // &
                  lrtrim(inttostr(pParamElm%Flag))
            endif

            ! Correct N-flags from file version 1.0:
            if (Opt%FileVersion == 1.0) then
               if ((Cat == cNValues) .and. (iAge == NumTrueAges(Opt%IndexInput) - 1) .and. &
                  (iYear < NumCatchYears(Opt%IndexInput) - 1) .and. (pParamElm%Flag == flKeepFixed)) then
                  ! N flags in oldest age in version 0 converts to flag 3
                  ! This opens up for using flag 0 as keeping fixed.
                  pParamElm%Flag = flSpecialCode
               endif
            end if
                        
            pParamElm%Value = strtofloat(s(ParFmtColStart(pfValue) : ParFmtColStart(pfValue) + c_ParFmt(pfValue)%Width - 1))
            ! Check the value range
            if (pParamElm%Value < 0.0d0) then
               Opt%RunErrorStr = "Parameter value ouside defined range: " // lrtrim(c_MetaParamSetup(Cat)%Text) // ", index:" // &
                  lrtrim(sIndex) // ", year:" // lrtrim(sYear) // ", age:" // lrtrim(sAge) // ", value:" // &
                  lrtrim(s(ParFmtColStart(pfValue) : ParFmtColStart(pfValue) + c_ParFmt(pfValue)%Width - 1))
            endif
            pParamElm%Descr = s(ParFmtColStart(pfText) : ParFmtColStart(pfText) + c_ParFmt(pfText)%Width - 1)
         endif
      endif
   enddo
   Close(cFileUnit)
end function LoadParameters


subroutine InitializeParameterStructures(Opt)
!   Purpose: This procedure sets up the parameter structure for each
!            parameter.
!   Assume:  Index input is properly read
!   Promise: Allocated and initalized parameters according to index input.
!   History: When        Who        What
!            22-04-07    Skalevik   Created
!
   implicit none
   type(TOptimisation), intent(inout) :: Opt
   integer :: Cat, iIndex, iYear, iAge, NIndices, NYears, NAges
   Integer :: IndexDim, YearDim, AgeDim
   character(len = 50) :: Descr
   type(TParamCube), pointer :: pParamCube
   type(TParamElm), pointer :: pParamElm
   type(TParamElm), pointer :: pNextYear
   type(TParamElm), pointer :: pNextAge

   call DeallocateParameters(Opt)
   allocate(Opt%ParamCubes(cFirstCategory : cLastCategory))
   do Cat = cFirstCategory, cLastCategory
      pParamCube => Opt%ParamCubes(Cat)
      ! Prepare and set dimension
      IndexDim = 1
      YearDim = 1
      AgeDim = 1
      if (SupportDim(Cat, cIndex)) then
         select case (Cat) 
            case (cCBL_Fac_Fleet, cCBL_Pow_Fleet, cGamma_Fleet)
               IndexDim = NumFleetIndices(Opt%IndexInput)
            case (cCBL_Fac_SSB, cCBL_Pow_SSB, cGamma_SSB)
               IndexDim = NumSSBIndices(Opt%IndexInput)
            case (cCBL_Fac_BM, cCBL_Pow_BM, cGamma_BM)
               IndexDim = NumBiomassIndices(Opt%IndexInput)
         end select
      endif
      if (SupportDim(Cat, cYear)) then
         YearDim = NumYears(Opt%IndexInput)
      endif
      if (SupportDim(Cat, cAge)) then
         AgeDim = NumAges(Opt%IndexInput)
      endif
      !if (Cat == cNValues) then
      !   AgeDim = AgeDim - 1
      !endif
      allocate(Opt%ParamCubes(Cat)%Elm(0 : IndexDim - 1, 0 : YearDim - 1, 0 : AgeDim - 1))
      ! Initialize content.
      NIndices = size(pParamCube%Elm, 1)
      do iIndex = 0, NIndices - 1
         NYears = size(pParamCube%Elm, 2)
         do iYear = NYears - 1, 0, -1
            NAges = size(pParamCube%Elm, 3)
            do iAge = NAges - 1, 0, -1
               pParamElm => Opt%ParamCubes(Cat)%Elm(iIndex, iYear, iAge)!GetParamElm(Cat, iIndex, iYear, iAge)
               allocate(pParamElm%Value)
               if( .not. associated(pParamElm)) then
                  cycle
               endif
               pParamElm%Flag = 0
               select case(Cat)
                  case(cCBL_Pow_Fleet : cCBL_Pow_BM, cGamma_Canum : cGamma_BM)
                     pParamElm%Value = 1
                  case default      
                     pParamElm%Value = 1 !Before 0
               end select
               pParamElm%Cat = Cat
               pParamElm%Index = iIndex
               pParamElm%Year = iYear
               pParamElm%Age = iAge
               Descr = lrtrim(c_MetaParamSetup(Cat)%Text)
               if (SupportDim(Cat, cIndex)) then
                  Descr = lrtrim(Descr) // ' ' // DimIndexToStr(Opt%IndexInput, Cat, cIndex, iIndex)
               endif
               if (SupportDim(Cat, cYear)) then
                  Descr = lrtrim(Descr) // ' ' // DimIndexToStr(Opt%IndexInput, Cat, cYear, iYear)
               endif
               if (SupportDim(Cat, cAge)) then
                  Descr = lrtrim(Descr) // ' ' // DimIndexToStr(Opt%IndexInput, Cat, cAge, iAge)
               endif
               pParamElm%Descr = Descr
               pParamElm%NextYear => GetParamElm(Opt, Cat, iIndex, iYear + 1, iAge, .true.)
               pParamElm%NextAge => GetParamElm(Opt, Cat, iIndex, iYear, iAge + 1, .true.)
            enddo
         enddo
      enddo
   enddo
end subroutine InitializeParameterStructures


function PValue(Opt, Cat, iIndex, iYear, iAge)
   implicit none
   type(TOptimisation), intent(in) :: Opt
   integer, intent(in) :: Cat, iIndex, iYear, iAge
   double precision :: PValue
   type(TParamElm), pointer :: pParamElm
   !write(*,*) Cat, iIndex, iYear, iAge
   !if (iYear > 18) call system("pause")
   PValue = Opt%ParamCubes(Cat)%Elm(iIndex, iYear, iAge)%Value
   !PValue = 0
   !pParamElm => GetParamElm(Opt, Cat, iIndex, iYear, iAge, .false.)
   !if (associated(pParamElm)) then
   !   PValue = pParamElm%Value
   !endif
end function PValue


function PFlag(Opt, Cat, iIndex, iYear, iAge)
   implicit none
   type(TOptimisation), intent(in) :: Opt
   integer, intent(in) :: Cat, iIndex, iYear, iAge
   integer :: PFlag
   type(TParamElm), pointer :: pParamElm
   PFlag = 0
   pParamElm => GetParamElm(Opt, Cat, iIndex, iYear, iAge, .false.)
   !if (associated(pParamElm)) then
      PFlag = pParamElm%Flag
   !endif
end function PFlag


subroutine SetPValue(Opt, Cat, iIndex, iYear, iAge, Val)
   implicit none
   type(TOptimisation), intent(in) :: Opt
   integer, intent(in) :: Cat, iIndex, iYear, iAge
   double precision, intent(in) :: Val
   type(TParamElm), pointer :: pParamElm
   pParamElm => GetParamElm(Opt, Cat, iIndex, iYear, iAge, .false.)
   !if (associated(pParamElm)) then
      pParamElm%Value = Val
   !endif
end subroutine SetPValue


recursive subroutine SetPValueEx(pParamElm, V) 
! Purpose: Determine if parameter should be estimated
! In     : Cat - category
!          iIndex - Index
!          iYear - Year
!          iAge - Age
   implicit none
   type(TParamElm), intent(inout) :: pParamElm
   double precision, intent(in) :: V
   pParamElm%Value = V
   if (associated(pParamElm%NextYear)) then
      if (pParamElm%NextYear%Flag == flEqualPrevYear) then
         call SetPValueEx(pParamElm%NextYear, V)
      end if
   end if
   if (associated(pParamElm%NextAge)) then
      if (pParamElm%NextAge%Flag == flEqualPrevAge) then
         call SetPValueEx(pParamElm%NextAge, V)
      end if
   end if
end subroutine SetPValueEx


subroutine DeallocateParameters(Opt)
!   Purpose: This procedure frees allocated memory
!   History: When        Who        What
!            02-06-07    Skalevik   Created
   implicit none
   type(TOptimisation), intent(inout) :: Opt
   integer :: Cat, iIndex, iYear, iAge, NIndices, NYears, NAges
   type(TParamCube), pointer :: pParamCube
   type(TParamElm), pointer :: pParamElm
   if (associated(Opt%ParamCubes)) then
      do Cat = cFirstCategory, cLastCategory
         pParamCube => Opt%ParamCubes(Cat)
         NIndices = size(pParamCube%Elm, 1)
         do iIndex = 0, NIndices - 1
            NYears = size(pParamCube%Elm, 2)
            do iYear = 0, NYears - 1
               NAges = size(pParamCube%Elm, 3)
               do iAge = 0, NAges - 1
                  pParamElm => GetParamElm(Opt, Cat, iIndex, iYear, iAge, .false.)
                  deallocate(pParamElm%Value)
               end do
            end do
         end do
         deallocate(pParamCube%Elm)
      end do
      deallocate(Opt%ParamCubes)
   end if
end subroutine DeallocateParameters


end module Parameter




